package assist.client.gwt.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.view.client.AsyncDataProvider;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.Range;
import com.google.web.bindery.event.shared.EventBus;

import assist.domain.model.Games;
import assist.domain.model.Player;
import assist.domain.model.Stats;
import assist.domain.model.Stats.Row;
import assist.domain.model.StatsQuery;
import assist.domain.model.StatsQuery.RowQuery;
import assist.service.gwt.interfaces.HockeyStatsService;
import assist.service.gwt.interfaces.HockeyStatsServiceAsync;

/**
 * Allows listeners to listen for changes to Stats.
 * Activities get/set the Queries, which update the Stats object.
 * which will then notify listeners.
 */
public class StatsProvider extends AsyncDataProvider<Stats.Row> 
    implements HasValueChangeHandlers<Stats>, ValueChangeHandler<List<Player>>
{
    private final HockeyStatsServiceAsync hockeyStatsService =
        GWT.create(HockeyStatsService.class); 
    
    private Stats stats = new Stats();
    
    
    private EventBus eventBus; 
    private GamesProvider gamesProvider;
    private HandlerManager handlerManager;
    private PlayersProvider playersProvider;

    private boolean doFetch = false;

    //private List<Stats.Row> rows = new ArrayList<Stats.Row>();
    
    public StatsProvider(EventBus eventBus, GamesProvider gamesProvider, 
            PlayersProvider playersProvider)
    {        
        this.eventBus = eventBus;
        this.handlerManager = new HandlerManager(this);
        //Register onGamesChange?  
        this.gamesProvider = gamesProvider;
        this.playersProvider = playersProvider;
        this.gamesProvider.addGamesChangeHandler(new ValueChangeHandler<Games>(){

            @Override
            public void onValueChange(ValueChangeEvent<Games> event)
            {
                if (StatsProvider.this.doFetch)
                {
                    StatsProvider.this.fetchStats();                    
                }
                else
                {
                    StatsProvider.this.reset();
                }
            }
            
        });
        this.playersProvider.addPlayersChangeHandler(this);
    }    
    
    /**
     * @param statsQuery the statsQuery to set
     */
    public void setStatsQuery(StatsQuery statsQuery)
    {
        //TODO: Need to notify displays that 
        this.stats.setStatsQuery(statsQuery);                
    }
    public static class StatsChangeEvent extends ValueChangeEvent<Stats>
    {
        protected StatsChangeEvent(Stats stats)
        {
            super(stats);
        }             
    }
    /**
     * Resets the uncalculated columns and rows in the table.
     * Call fetchStats to calculate the stats.
     */
    public void reset()
    {
        this.stats.clear();
        if (this.stats.getStatsQuery() == null || this.gamesProvider.getGames() == null)
        {
            return;
        }
        for (RowQuery rowQuery : this.stats.getStatsQuery().getRowQueries())
        {
            Row row = new Row();
            row.setRowQuery(rowQuery);            
            addUncalculatedRow(row);             
        }        
        this.fireStatsChanged();
    }
    
    
    private void fireStatsChanged()
    {
        this.fireEvent(new StatsChangeEvent(this.stats));
    }

    private void formatRowTitles()
    {
        for (Row row : this.stats)
        {
            formatTitle(row.getRowQuery());
        }
        updateRowData();
    }

    private void formatTitle(RowQuery rowQuery)
    {
        Set<Long> playerIds = rowQuery.getPlayerIds();
        if (playerIds.isEmpty())
        {
            rowQuery.setTitle(rowQuery.getTeam().getAbbreviation() + " (All Team)");
            return;
        }
        String title = "";
        for (Long playerId : playerIds)
        {
            if (!title.isEmpty())
            {
                title += " + ";
            }
            Player player = this.playersProvider.getPlayer(playerId);
            if (player == null)
            {
                title += "Loading Player ID: " + playerId;
            }
            else
            {
                title += player.getName();
            }
        }
        rowQuery.setTitle(title);        
    }
    public void fetchStats()
    {        
        this.stats.clear();
        if (this.gamesProvider.getGames() == null)
        {
            this.doFetch = true;
            return;
        }
        
        //TODO: Need listener for Games here (On Refresh/bookmarks)
        //See PlayersProvider
        this.stats.getStatsQuery().setGameIds(this.gamesProvider.getGames().getGameIds());
        //For each Individual Player
        for (RowQuery rowQuery : this.stats.getStatsQuery().getRowQueries())
        {
            //TODO: Result here is Async.
             fetchStatsRow(rowQuery);
            //TODO: This is in Async reply
        }        
    }
    
    

    private void fetchStatsRow(RowQuery rowQuery)
    {
         hockeyStatsService.getStatsRow(rowQuery, 
                 new AsyncCallback<Stats.Row>() {
             public void onFailure(Throwable caught) {
                 Window.alert("Remote Procedure Call - Failure" + caught);
                 caught.printStackTrace();
             }

             public void onSuccess(Stats.Row row) 
             {                 
                 addCalculatedRow(row);
             }
         });              
    }
    
    private void addRow(Row row)
    {
        formatTitle(row.getRowQuery());
        this.stats.add(row);        
    }
    
    private void updateRowData()
    {
        this.updateRowData(0, this.stats);
        this.updateRowCount(this.stats.size(), true);        
    }
    
    private void addCalculatedRow(Row row)
    {
        this.addRow(row);
System.err.println("Adding row: " + this.stats.isCalculated()); 
        if (this.stats.isCalculated())
        {
            this.updateRowData();
            this.doFetch = false;
        }
        else
        {
System.err.println("TODO: Impleemnt this event firer, catch in the table and update progress widget.");
      //      this.fireCalcProgressEvent();
        }        
    }
    
    
    private void addUncalculatedRow(Row row)
    {
        this.addRow(row);
        this.updateRowData();
    }
    /**
     * @return the statsQuery
     */
    public StatsQuery getStatsQuery()
    {
        return this.stats.getStatsQuery();
    }
    @Override
    public void fireEvent(GwtEvent<?> event)
    {
        this.eventBus.fireEvent(event);
        this.handlerManager.fireEvent(event);       
    }
    @Override
    public HandlerRegistration addValueChangeHandler(
            ValueChangeHandler<Stats> handler)
    {
        // TODO Auto-generated method stub
        return this.handlerManager.addHandler(StatsChangeEvent.getType(), handler);
    }
    @Override
    protected void onRangeChanged(HasData<Row> display)
    {
        if (this.stats == null || this.stats.isEmpty())
        {
            display.setRowData(0, new ArrayList<Stats.Row>());
            return;
        }
        final Range range = display.getVisibleRange();
        int start = range.getStart();
        //int end = start + range.getLength();
        display.setRowData(start, this.stats);

    }
    @Override
    public void onValueChange(ValueChangeEvent<List<Player>> event)
    {
        if (!this.doFetch)
        {
            this.formatRowTitles();
        }
    }
    
    public PlayerWowy getPlayerWowy(Player player)
    {
        return new PlayerWowy(player, getStatsQuery().getPlayerWowyIds(player));
    }
    
    public class PlayerWowy
    {
        private Set<Player> playerWowys = new TreeSet<Player>();
        private Player player;
        PlayerWowy(Player player, Set<Long> playerWowyIds)
        {
            this.player = player;
            if (playerWowyIds != null)
            {
                for (Long playerId : playerWowyIds)
                {
                    this.playerWowys.add(playersProvider.getPlayer(playerId));
                }
            }
        }
        public Set<Player> getPlayerWowys()
        {            
            return this.playerWowys;
        }
        public Player getPlayer()
        {
            return this.player;
        }
    }    
}
